﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using DocumentFormat.OpenXml.Packaging;
using OpenPowerPoint.Collection;
using OpenPowerPoint.Exceptions;

namespace OpenPowerPoint.Slides
{
    /// <summary>
    /// Represents a Slide Master
    /// </summary>
    public class SlideMaster : BaseSlide
    {
        /// <summary>
        /// The slideMaster part
        /// </summary>
        /// <remarks>This variable will only be temporary accessible since it is only needed for saving. But nethertheless we need it, because the <see cref="Slide"/> also needs a reference to its master while saving.<seealso cref="Slide"/></remarks>
        private SlideMasterPart _part = null;

        #region Load/Save
        /// <summary>
        /// Generates a Slide Master out of the Slide Master part
        /// </summary>
        /// <param name="part">The SlideMasterPart to use</param>
        /// <returns>A SlideMaster instance according to the SlideMasterPart</returns>
        public static SlideMaster Load(SlideMasterPart part)
        {
            var ret = new SlideMaster();

            ret.Theme = Theme.Load(part.ThemePart.Theme);

            //load layouts
            int layouts = part.SlideLayoutParts.Count();

            for (int i = 0; i < layouts; i++)
            {
                ret.Layouts.Add(SlideLayout.Load(part.SlideLayoutParts.ElementAt(i)));
            }

            //Load Content
            ret.LoadCommonSlideData(part.SlideMaster.CommonSlideData);

            ret.Preserve = part.SlideMaster.Preserve ?? true;

            return ret;
        }

        /// <summary>
        /// Saves the current Master slide into the presentation
        /// </summary>
        /// <param name="presentation">The presentation to save to</param>
        public void Save(ref PresentationPart presentation)
        {
            string rId = RelationResolver.GetNextRelationId();

            _part = presentation.AddNewPart<SlideMasterPart>(rId);

            _part.SlideMaster = new DocumentFormat.OpenXml.Presentation.SlideMaster();
            _part.SlideMaster.Preserve = Preserve;
        }

        /// <summary>
        /// Performs a cleanup of all temporary data
        /// </summary>
        public void Saved()
        {
            _part = null;
        }
        #endregion

        #region Initializer
        /// <summary>
        /// Generates a new Slide Master
        /// </summary>
        /// <remarks>We don't need an default layout here, because Load will do it for us.</remarks>
        private SlideMaster()
            : base()
        {
            Layouts = new NotifyableList<SlideLayout>();
            Theme = Themes.Larissa;
        }

        /// <summary>
        /// Generates a new Slide Master with an initial layout
        /// </summary>
        /// <param name="initialLayout">The default layout for the Slide Master</param>
        /// 
        public SlideMaster(SlideLayout initialLayout)
            : this()
        {
            Layouts.Add(initialLayout);
        }

        #endregion

        #region Properties
        /// <summary>
        /// Returns the generated Master Slide Part
        /// </summary>
        internal SlideMasterPart Part
        {
            get { return _part; }
        }


        public static readonly DependencyProperty ThemeProperty =
            DependencyProperty.Register("Theme", typeof(Theme), typeof(SlideMaster),
                                        new PropertyMetadata(default(Theme), (o, e) =>
                                                                                  {
                                                                                      if (e.NewValue != null)
                                                                                          ((Theme)e.NewValue).PropertyChanged += ((SlideMaster)o).OnPropertyChanged;
                                                                                      if (e.OldValue != null)
                                                                                          ((Theme)e.OldValue).PropertyChanged -= ((SlideMaster)o).OnPropertyChanged;

                                                                                      ((SlideMaster)o).OnPropertyChanged(e, new SProperty(ThemeProperty, e.OldValue, e.NewValue));
                                                                                  }));

        /// <summary>
        /// The Theme used by this master
        /// </summary>
        public Theme Theme
        {
            get { return (Theme)GetValue(ThemeProperty); }
            set { SetValue(ThemeProperty, value); }
        }

        public static readonly DependencyProperty LayoutsProperty =
            DependencyProperty.Register("Layouts", typeof(NotifyableList<SlideLayout>), typeof(SlideMaster),
                                        new PropertyMetadata(new NotifyableList<SlideLayout>(), (o, e) =>
                                                                                                       {
                                                                                                           if (e.NewValue != null)
                                                                                                               ((NotifyableList<SlideLayout>)e.NewValue).PropertyChanged +=
                                                                                                                   ((SlideMaster)o).OnPropertyChanged;
                                                                                                           if (e.OldValue != null)
                                                                                                               ((NotifyableList<SlideLayout>)e.OldValue).PropertyChanged -=
                                                                                                                   ((SlideMaster)o).OnPropertyChanged;
                                                                                                       }));
        /// <summary>
        /// The Layouts this Master has
        /// </summary>
        public ObservableCollection<SlideLayout> Layouts
        {
            get { return (ObservableCollection<SlideLayout>)GetValue(LayoutsProperty); }
            set { SetValue(LayoutsProperty, value); }
        }

        public static readonly DependencyProperty PreserverProperty = DependencyProperty.Register("Preserve", typeof(bool), typeof(SlideMaster), new PropertyMetadata(true));

        /// <summary>
        /// Defines whether or not to keep unused SlideMasters on saving
        /// </summary>
        public bool Preserve
        {
            get { return (bool)GetValue(PreserverProperty); }
            set { SetValue(PreserverProperty, value); }
        }
        #endregion

        #region Functions
        #region Call-Trough
        /// <summary>
        /// Adds a new Layout to the Master
        /// </summary>
        /// <returns>The created Layout</returns>
        public SlideLayout AddLayout()
        {
            SlideLayout layout = new SlideLayout();
            Layouts.Add(layout);

            return layout;
        }

        /// <summary>
        /// Returns the layout at a position
        /// </summary>
        /// <param name="index">The index of the layout</param>
        /// <returns></returns>
        /// <exception cref="ArgumentOutOfRangeException">Index is out of Range</exception>
        public SlideLayout GetLayout(int index)
        {
            if (index < 0 || index >= Layouts.Count)
                throw new ArgumentOutOfRangeException("Index was out of range");

            return Layouts[index];
        }

        /// <summary>
        /// Removes a layout at a position
        /// </summary>
        /// <param name="index">The position of the layout to remove</param>
        /// <exception cref="ArgumentOutOfRange">Index is out of Range</exception>
        public void RemoveLayout(int index)
        {
            if (index < 0 || index >= Layouts.Count)
                throw new ArgumentOutOfRangeException("Index was out of range");

            Layouts.RemoveAt(index);
        }

        /// <summary>
        /// The total number of layouts contained in this master slide
        /// </summary>
        /// <returns></returns>
        public int LayoutCount()
        {
            return Layouts.Count;
        }
        #endregion
        #endregion

        #region INotify
        /// <summary>
        /// Handles changed properties in Child lists
        /// </summary>
        /// <param name="sender">The source of the event</param>
        /// <param name="property">Information about the property and its value</param>
        /// <exception cref="ListNotClearable">Gets thrown when trying to remove too much items, so that no item is left.</exception>
        /// <remarks>This Handler does a little bit more than only handing the event to the next handler. It also handles remove and clear calls on the LayoutList. To avoid zero elements in this list,
        /// an exception gets thrown if so. Except for clear - clear removes all items except the first one.</remarks>
        internal override void OnPropertyChanged(object sender, SProperty property)
        {
            if (sender == Layouts)
            {
                //Handle list changes here

                //list cannot be cleared - one item has to remain!
                if (property.IsClear())
                {
                    property.NewValue = "";

                    //Remove all except the first one
                    ((NotifyableList<SlideLayout>)Layouts).RemoveRange(1, Layouts.Count - 1);
                    return; //Avoid doubled event handling - event for remove range is enough.
                }

                if (property.IsRemove() && Layouts.Count == 1)
                {
                    property.NewValue = ""; //Can't remove last item

                    throw new ListNotClearable("Layouts cannot be cleared. There has to be at least one Layout.");
                }
            }



            base.OnPropertyChanged(sender, property);
        }
        #endregion
    }
}
